import difflib
from AStock.ASSetting import settings
from AStock.ASEvent import Worker
from AStock.ASUtil import ASHttpParser, date_to_dot_format, date_to_bar_format
from AStock.ASSetting import cons
from AStock.ASData import financial_dict_chinese_to_id


class ASFinancialCrawler(Worker):
    
    def __init__(self):
        super().__init__()

        self._xpath_hexun = '//div[@id="zaiyaocontent"]/table'
        self._xpath_sina_zcfzb = '//table[@id="BalanceSheetNewTable0"]'
        self._xpath_sina_lrb = '//table[@id="ProfitStatementNewTable0"]'
        self._xpath_sina_xjllb = '//table[@id="ProfitStatementNewTable0"]'
        self._xpath_sina_cwbl = '//table[@id="BalanceSheetNewTable0"]'
        
        self._param_dict = {}

        if settings.financial_source_zcfzb == cons.CrawlSource.HEXUN:
            self._param_dict['url_zcfzb'] = cons.HEXUN_FINANCIAL_ZCFZB  # 资产负债表
            self._param_dict['xpath_zcfzb'] = self._xpath_hexun
            self._param_dict['code_zcfzb'] = self._hexun_code
            self._param_dict['date_zcfzb'] = self._hexun_date
            self._param_dict['proxy_zcfzb'] = None
            self._param_dict['df_handler_zcfzb'] = None
            self._param_dict['save_handler_zcfzb'] = None
            self._param_dict['table_handler_zcfzb'] = None
        elif settings.financial_source_zcfzb == cons.CrawlSource.SINA:
            self._param_dict['url_zcfzb'] = cons.SINA_FINANCIAL_ZCFZB  # 资产负债表
            self._param_dict['xpath_zcfzb'] = self._xpath_sina_zcfzb
            self._param_dict['code_zcfzb'] = self._sina_code
            self._param_dict['date_zcfzb'] = self._sina_date
            self._param_dict['proxy_zcfzb'] = None
            self._param_dict['df_handler_zcfzb'] = self._df_handler_sina
            self._param_dict['table_handler_zcfzb'] = None

        if settings.financial_source_cwbl == cons.CrawlSource.HEXUN:
            self._param_dict['url_cwbl'] = cons.HEXUN_FINANCIAL_CWBL  # 财务比率
            self._param_dict['xpath_cwbl'] = self._xpath_hexun
            self._param_dict['code_cwbl'] = self._hexun_code
            self._param_dict['date_cwbl'] = self._hexun_date
            self._param_dict['proxy_cwbl'] = None
            self._param_dict['df_handler_cwbl'] = None
            self._param_dict['save_handler_cwbl'] = None
            self._param_dict['table_handler_cwbl'] = None
        elif settings.financial_source_cwbl == cons.CrawlSource.SINA:
            self._param_dict['url_cwbl'] = cons.SINA_FINANCIAL_CWBL  # 财务比率
            self._param_dict['xpath_cwbl'] = self._xpath_sina_cwbl
            self._param_dict['code_cwbl'] = self._sina_code
            self._param_dict['date_cwbl'] = self._sina_date
            self._param_dict['proxy_cwbl'] = None
            self._param_dict['df_handler_cwbl'] = self._df_handler_sina
            self._param_dict['save_handler_cwbl'] = None
            self._param_dict['table_handler_cwbl'] = None

        if settings.financial_source_lrb == cons.CrawlSource.HEXUN:
            self._param_dict['url_lrb'] = cons.HEXUN_FINANCIAL_LRB  # 利润表
            self._param_dict['xpath_lrb'] = self._xpath_hexun
            self._param_dict['code_lrb'] = self._hexun_code
            self._param_dict['date_lrb'] = self._hexun_date
            self._param_dict['proxy_lrb'] = None
            self._param_dict['df_handler_lrb'] = None
            self._param_dict['save_handler_lrb'] = None
            self._param_dict['table_handler_lrb'] = None
        elif settings.financial_source_lrb == cons.CrawlSource.SINA:
            self._param_dict['url_lrb'] = cons.SINA_FINANCIAL_LRB  # 利润表
            self._param_dict['xpath_lrb'] = self._xpath_sina_lrb
            self._param_dict['code_lrb'] = self._sina_code
            self._param_dict['date_lrb'] = self._sina_date
            self._param_dict['proxy_lrb'] = None
            self._param_dict['df_handler_lrb'] = self._df_handler_sina
            self._param_dict['table_handler_lrb'] = None

        if settings.financial_source_xjllb == cons.CrawlSource.HEXUN:
            self._param_dict['url_xjllb'] = cons.HEXUN_FINANCIAL_XJLLB  # 现金流量表
            self._param_dict['xpath_xjllb'] = self._xpath_hexun
            self._param_dict['code_xjllb'] = self._hexun_code
            self._param_dict['date_xjllb'] = self._hexun_date
            self._param_dict['proxy_xjllb'] = None
            self._param_dict['df_handler_xjllb'] = None
            self._param_dict['save_handler_xjllb'] = None
            self._param_dict['table_handler_xjllb'] = None
        elif settings.financial_source_xjllb == cons.CrawlSource.SINA:
            self._param_dict['url_xjllb'] = cons.SINA_FINANCIAL_XJLLB  # 现金流量表
            self._param_dict['xpath_xjllb'] = self._xpath_sina_xjllb
            self._param_dict['code_xjllb'] = self._sina_code
            self._param_dict['date_xjllb'] = self._sina_date
            self._param_dict['proxy_xjllb'] = None
            self._param_dict['df_handler_xjllb'] = self._df_handler_sina
            self._param_dict['table_handler_xjllb'] = None

    @staticmethod
    def _hexun_code(code):
        return code

    @staticmethod
    def _sina_code(code):
        return code

    @staticmethod
    def _hexun_date(date):
        """
        和讯网的季报url日期格式是yyyy.mm.dd
        一季报url的日期是 yyyy.03.15
        :param date:
        :return:
        """
        if date[4:] == '0331':
            date = date[:4] + '0315'
        return date_to_dot_format(date)

    @staticmethod
    def _sina_date(date):
        """
        新浪网季报日期格式是yyyy-mm-dd
        :param date:
        :return:
        """
        return date_to_bar_format(date)

    @staticmethod
    def _proxy():
        # TODO move proxy to ASUtil
        return None

    @staticmethod
    def _df_handler_sina(df, **kwarg):
        """
        新浪财报需要根据财报日期选取对应的dataframe列
        :param df:
        :param kwarg:
        :return:
        """
        if df.shape[0] == 0:
            return df

        report_date = kwarg['report_date']
        first_row = df.iloc[0]  # Series
        res_df = None
        for index, value in first_row.items():
            if value == report_date:
                # 取第0列和第index列
                res_df = df.iloc[:, [0, index]]
                break
        return res_df

    def run(self, event_data):
        """
        执行抓取财务数据任务
        :param event_data: dict，包括：
            event_type: zcfzb 抓取资产负债表
                        lrb 抓取利润表
                        xjllb 抓取现金流量表
                        cwbl 抓取财务比率
            code: str，股票代码，格式为 600001
            report_date: str，报表日期，格式为 yyyymmdd

        :return: dict，
            失败时，返回的字典为 {'error': 'error string'}
            成功时，返回的字典为 event_data + {'df': DataFrame}

            DataFrame，示例：
                     0              1
            0     会计年度     2020.06.30
            1     货币资金  9562883398.78
            2  交易性金融资产  5843176570.26
            3     应收票据   115423572.58
            4     应收账款  4338118283.14
        """
        try:
            event_type = event_data['event_type']
            code = event_data['code']
            report_date = event_data['report_date']
        except KeyError as e:
            return {'error': 'key error in event data, key: {}'.format(e)}

        if event_type not in ['zcfzb', 'lrb', 'xjllb', 'cwbl']:
            return {'error': 'event type error: {}'.format(event_type)}

        code_mapping = self._param_dict.get('code_' + event_type)
        if code_mapping:
            code = code_mapping(code)
        date_mapping = self._param_dict.get('date_' + event_type)
        if date_mapping:
            report_date = date_mapping(report_date)

        url = self._param_dict['url_' + event_type].format(
            stockid=code, reportdate=report_date, reportyear=report_date[:4])
        print(url)
        parser = ASHttpParser(url, cons.HEADER, self._param_dict.get('proxy_' + event_type))
        df = parser.parse_table(
            xpath=self._param_dict['xpath_' + event_type],
            df_handler=self._param_dict.get('df_handler_' + event_type),
            table_handler=self._param_dict.get('table_handler_' + event_type),
            code=code,
            report_date=report_date
        )
        res = event_data.copy()
        res['df'] = df
        return res


class ASFinancialSaver(Worker):

    def __init__(self):
        super().__init__()
        self._financial_dict = financial_dict_chinese_to_id

        self._param_dict = {}
        if settings.financial_source_zcfzb == cons.CrawlSource.SINA:
            self._param_dict['save_handler_zcfzb'] = self._save_handler_sina

        if settings.financial_source_lrb == cons.CrawlSource.SINA:
            self._param_dict['save_handler_lrb'] = self._save_handler_sina

        if settings.financial_source_xjllb == cons.CrawlSource.SINA:
            self._param_dict['save_handler_xjllb'] = self._save_handler_sina

        if settings.financial_source_cwbl == cons.CrawlSource.HEXUN:
            self._param_dict['save_handler_cwbl'] = self._save_handler_hexun_cwbl

        self._specified_field_mapping = {
            '总资产收益率': None,  # 强制不映射该字段
            '负债及股东权益总计': '073',
            # '现金的期末余额': '008'
            '每股经营活动产生的现金流量净额': None,
            '扣除非经常损益后的净利润率': None,
            '报告日期': None
        }

    @staticmethod
    def _save_handler_sina(field_name, value):
        """
        新浪财报（资产负债表、利润表、现金流量表）的单位是万元，需要转为元
        :param field_name:
        :param value:
        :return:
        """
        return value * 10000.0

    @staticmethod
    def _save_handler_hexun_cwbl(field_name, value):
        """
        和讯的财务比率中的百分比需要乘100
        :param field_name:
        :param value:
        :return:
        """
        # TODO 根据 field_name 对百分比类型乘100
        if field_name == '006':
            value = value * 100.0
        return value

    def run(self, event_data):
        """
        抓取的财务报表数据存储到mongodb
        :param event_data: dict，包括：
            event_type: zcfzb 资产负债表
                        lrb 利润表
                        xjllb 现金流量表
                        cwbl 财务比率
            code: str，股票代码，格式为 600001
            report_date: str，报表日期，格式为 yyyymmdd
            df: DataFrame，报表数据，示例：
                     0              1
            0     会计年度     2020.06.30
            1     货币资金  9562883398.78
            2  交易性金融资产  5843176570.26
            3     应收票据   115423572.58
            4     应收账款  4338118283.14
        :return: dict，
            成功：{'succ': 'saved: 600001  20200630    financial zcfzb'}
            失败：{'fail': 'failed to save: 600001  20200630    financial zcfzb'}
        """
        try:
            event_type = event_data['event_type']
            code = event_data['code']
            report_date = event_data['report_date']
            df = event_data['df']
        except KeyError as e:
            return {'fail': 'key error in event data, key: {}'.format(e)}

        if event_type not in ['zcfzb', 'lrb', 'xjllb', 'cwbl']:
            return {'fail': 'event type error: {}'.format(event_type)}

        fail = 'failed to save: {}   {}   financial {}'.format(
            code, report_date, event_type)

        if df is None:
            return {'fail': fail + '    empty financial data frame'}

        return self._save(event_type, code, report_date, df)

    def _save(self, event_type, code, report_date, df):
        succ = 'saved: {}   {}   financial {}'.format(code, report_date, event_type)
        fail = 'failed to save: {}   {}   financial {}'.format(
            code, report_date, event_type)
        doc = {'code': code, 'report_date': int(report_date)}
        for row in df.itertuples():
            # row[0], row[1], row[2] 等同于
            # row.Index, row.c1, row.c2
            field_name = self._fuzzy_match(row[1])
            if field_name is None:
                # print('no fuzzy match: {} {} {}'.format(code, report_date, row[1]))
                continue
            try:
                v = float(row[2])
                save_handler = self._param_dict.get('save_handler_' + event_type)
                if save_handler:
                    v = save_handler(field_name, v)
                doc[field_name] = v
            except ValueError:
                doc[field_name] = 0.0
        try:
            # print(doc)
            coll = settings.database.financial  # 文档集合对象
            coll.update_one({'code': code, 'report_date': int(report_date)}, {'$set': doc}, upsert=True)
        except Exception as e:
            return {'fail': fail + '    ' + str(e)}

        return {'succ': succ}

    def _fuzzy_match(self, cn_financial_item):
        # TODO 财务比率的匹配不太准确
        # 指定的映射中存在，则直接返回结果
        if cn_financial_item in self._specified_field_mapping:
            return self._specified_field_mapping[cn_financial_item]

        matches = difflib.get_close_matches(
            cn_financial_item, self._financial_dict.keys(), n=1, cutoff=0.8)
        # print('fuzzy match: {} -> {}'.format(cn_financial_item, matches))
        if matches:
            return self._financial_dict.get(matches[0])  # 只需要前三位数字代码
        return None
